package abc.base;

import abc.annotation.Associative;
import abc.annotation.TargetAction;
import abc.config.DatasourceConfigure;
import abc.database.ElExpressionQueryHelper;
import abc.enums.ActionType;
import abc.filter.RequestMatchSchemaFilter;
import abc.model.IEntity;
import abc.utils.AssertUtil;
import abc.utils.JacksonUtil;
import abc.utils.ReflectionUtil;
import abc.utils.StringUtil;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;

import java.io.IOException;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

@Slf4j
public abstract class BaseDataExecuor {

    @Autowired
    protected ApplicationContext applicationContext;

    @Autowired
    protected RequestMatchSchemaFilter requestMatchSchemaFilter;

    @Autowired
    protected DatasourceConfigure datasourceConfigure;

    @Autowired
    protected SqlSessionTemplate sqlSessionTemplate;

    @Value("${mybatis-plus.global-config.db-config.logic-delete-field:}")
    protected String logicDeleteFiled;

    @Value("${mybatis-plus.global-config.db-config.logic-delete-value:1}")
    protected Integer logicDeleteValue;

    @Value("${mybatis-plus.global-config.db-config.logic-not-delete-value:0}")
    protected Integer logicNotDeleteValue;

    @Value("${abc.api.associative-query-enable:false}")
    protected Boolean associativeQueryEnable = false;

    @Value("${abc.api.associative-update-enable:false}")
    protected Boolean associativeUpdateEnable = false;

    @Value("${abc.api.keyword-search-enable:false}")
    protected Boolean enableKeywordSearch = false;

    @Value("${abc.api.keyword-splitter:}")
    protected String keywordSplitter;

    @Value("${abc.api.keyword-search-el-expression-open:false}")
    protected Boolean openKeywordElExpression;

    @Autowired
    protected ElExpressionQueryHelper elExpressionQueryHelper;

    public BaseDataExecuor() {
    }
    public BaseDataExecuor(DatasourceConfigure datasourceConfigure) {
        this.datasourceConfigure = datasourceConfigure;
        this.init();
    }

    @PostConstruct
    public void init() {
        if(datasourceConfigure == null ||
                datasourceConfigure.getDatasource() == null ||
                datasourceConfigure.getDatasource().entrySet().size()<1) {
            log.warn("Can not find the datasource config!!!");
            return;
        }
        elExpressionQueryHelper = new ElExpressionQueryHelper(datasourceConfigure);
        if(StringUtil.isEmpty(keywordSplitter)) {
            keywordSplitter = " ";
        }
        keywordSplitter = keywordSplitter.replace(" ", "\\ ");
    }

    /**
     * 获取sqlSession
     * @return
     */
    public SqlSession getSqlSession() throws SQLException {
        return getSqlSession(true);
    }

    /**
     * 获取sqlSession
     * @return
     */
    public SqlSession getSqlSession(boolean autoCommit) throws SQLException {
        SqlSession session = SqlSessionUtils.getSqlSession(sqlSessionTemplate.getSqlSessionFactory(),
                sqlSessionTemplate.getExecutorType(), sqlSessionTemplate.getPersistenceExceptionTranslator());
        if(!autoCommit && session.getConnection().getAutoCommit()) {
            session.getConnection().setAutoCommit(false);
        }
        return session;
    }

    /**
     * 提交sqlSession
     * @param session
     */
    public void commitSqlSession(SqlSession session) {
        try {
            session.getConnection().setAutoCommit(true);
        } catch (SQLException e) {
            log.error(e.getMessage());
        }
    }

    /**
     * 关闭sqlSession
     * @param session
     */
    public void closeSqlSession(SqlSession session) {
        SqlSessionUtils.closeSqlSession(session, sqlSessionTemplate.getSqlSessionFactory());
    }

    public Boolean hasField(String dbname, String table, String field) {
        verifier(dbname, table);
        if(StringUtil.isEmpty(field) || !elExpressionQueryHelper.getColumnsMap().containsKey(dbname)) {
            return false;
        }
        return elExpressionQueryHelper.getColumnsMap().get(dbname).stream().anyMatch(a-> table.equals(a.get("table").toString()) && field.equals(a.get("name")));
    }

    public List<Map<String, Object>> getCloumns(String dbname) {
        return elExpressionQueryHelper.getColumnsMap().get(dbname);
    }

    public Map<String, Map<String, Object>> getTableMap(String dbname) {
        return elExpressionQueryHelper.getTableMap(dbname);
    }

    protected BaseModel fillPrimaryKeys2Model(String dbname, String table, BaseModel model, Long id) {
        List<String> primaryKeyNames = elExpressionQueryHelper.getPrimaryKeyNames(dbname, table);
        if(primaryKeyNames.size() == 1) {
            ReflectionUtil.setFieldValue(model, primaryKeyNames.get(0), id);
        }

        return model;
    }

    protected void verifier(String dbname, String table) {
        AssertUtil.isFalse(elExpressionQueryHelper.getColumnsMap().containsKey(dbname), "Can not find the dbname!");
        AssertUtil.isFalse(elExpressionQueryHelper.getColumnsMap().get(dbname).stream().anyMatch(a-> StringUtil.getHumpName(table, true).equals(StringUtil.getHumpName(a.get("table").toString(), true))), "Can not find the table name!");
    }

    /***
     * 转换为视图对象
     * @param data
     * @param <T>
     * @return
     */
    public <T> Object toVo(T data, ActionType actionType) throws SQLException {
        SqlSession session = getSqlSession();
        try {
            if (data instanceof BaseModel<?>) {
                var vo = toVoBean((BaseModel) data, actionType);
                if (associativeQueryEnable) {
                    associativeQuery(requestMatchSchemaFilter.getDBName(),
                            requestMatchSchemaFilter.getTableName(),
                            (BaseModel) data, (BaseEntity) vo, session);
                }
                return vo;
            } else if ((data instanceof List<?>) && ((List<?>) data).size()>0 && (((List<?>) data).get(0) instanceof BaseModel)) {
                List list = new ArrayList();
                for (var i = 0; i < ((List<?>) data).size(); i++) {
                    var item = ((List<?>) data).get(i);
                    var vo = toVoBean((BaseModel) item, actionType);
                    if (associativeQueryEnable) {
                        associativeQuery(requestMatchSchemaFilter.getDBName(),
                                requestMatchSchemaFilter.getTableName(),
                                (BaseModel) item, (BaseEntity) vo, session);
                    }
                    list.add(vo);
                }
                return list;
            }
        }
        catch (SQLException | IOException e) {
            log.error(e.getMessage(), e);
        }
        finally {
            closeSqlSession(session);
        }

        return data;
    }

    /***
     * 关联查询
     * @param dbname
     * @param table
     * @param model
     * @param entity
     * @param session
     * @throws SQLException
     */
    protected void associativeQuery(String dbname, String table, BaseModel model, BaseEntity entity, SqlSession session) throws SQLException {
        if(entity==null || !associativeQueryEnable ||
                StringUtil.isNotEmpty(dbname) ||
                !elExpressionQueryHelper.getTablesMap().containsKey(dbname) ||
                !elExpressionQueryHelper.getTableMap(dbname).keySet().stream()
                        .map(a-> StringUtil.getHumpName(a, true))
                        .collect(Collectors.toList()).contains(table)) {
            return;
        }

        List<Associative> modelAnns = Arrays.stream(entity.getClass().getDeclaredFields())
                .filter(a -> a.getAnnotation(Associative.class) != null)
                .map(a -> a.getAnnotation(Associative.class))
                .collect(Collectors.toList());

        if(modelAnns.size() == 0) {
            modelAnns = Arrays.stream(entity.getClass().getSuperclass().getDeclaredFields())
                    .filter(a -> a.getAnnotation(Associative.class) != null)
                    .map(a -> a.getAnnotation(Associative.class))
                    .collect(Collectors.toList());
        }

        modelAnns.forEach(a-> {
            if(!elExpressionQueryHelper.getTableMap(dbname).keySet().contains(a.table())) {
                return;
            }
            BaseModel bean = applicationContext.getBean(StringUtil.getHumpName(a.table(), true) + "Model", BaseModel.class);
            List<String> pkList = elExpressionQueryHelper.getPrimaryKeyNames(dbname, table);
            pkList.forEach(pk-> ReflectionUtil.setFieldValue(bean, StringUtil.getHumpName(table, true)+"Id", ReflectionUtil.getFieldValue(model, pk)));
            MappedStatement mappedStatement = bean.getMappedStatement(SqlMethod.SELECT_LIST);
            Map<String, Object> map = CollectionUtils.newHashMapWithExpectedSize(1);
            map.put(Constants.WRAPPER, elExpressionQueryHelper.getOrderDescByPkQueryWrapper(elExpressionQueryHelper.getDefaultQueryWrapper(bean), dbname, a.table()));
            List result = session.selectList(mappedStatement.getId(), map);
            List volist = new ArrayList();
            result.forEach(m-> {
                try {
                    volist.add(toVoBean((BaseModel)m, ActionType.ALL));
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            });
            ReflectionUtil.setFieldValue(entity, String.format("%sList", StringUtil.getHumpName(a.table(), true)), volist);
        });
    }

    /***
     * 关联更新
     * @param dbname
     * @param table
     * @param model
     * @param session
     */
    protected Boolean associativeUpdate(String dbname, String table, BaseModel model, SqlSession session) {
        AtomicReference<Boolean> result = new AtomicReference<>(false);
        if(!associativeQueryEnable ||
                !elExpressionQueryHelper.getTablesMap().containsKey(dbname) ||
                !elExpressionQueryHelper.getTableMap(dbname).keySet().stream()
                        .map(a-> StringUtil.getHumpName(a, true))
                        .collect(Collectors.toList()).contains(table)) {
            return result.get();
        }

        List<Associative> modelAnns = Arrays.stream(model.getClass().getDeclaredFields())
                .filter(a -> a.getAnnotation(Associative.class) != null)
                .map(a -> a.getAnnotation(Associative.class))
                .collect(Collectors.toList());

        if(modelAnns.size() == 0) {
            modelAnns = Arrays.stream(model.getClass().getSuperclass().getDeclaredFields())
                    .filter(a -> a.getAnnotation(Associative.class) != null)
                    .map(a -> a.getAnnotation(Associative.class))
                    .collect(Collectors.toList());
        }
        Optional<String> tb = elExpressionQueryHelper.getTableMap(dbname).keySet().stream().filter(a -> StringUtil.getHumpName(a, true).equals(table)).findFirst();
        if(!tb.isPresent()) {
            return false;
        }
        modelAnns.forEach(a-> {
            if(!elExpressionQueryHelper.getTableMap(dbname).keySet().contains(a.table())) {
                return;
            }
            List<BaseModel> datas = (List) ReflectionUtil.getFieldValue(model, String.format("%sList", StringUtil.getHumpName(a.table(), true)));
            if(datas == null || datas.size()<1) {
                return;
            }

            List<String> pkList = elExpressionQueryHelper.getPrimaryKeyNames(dbname, table);

            Map<String, Object> valMap = new HashMap<>();
            pkList.forEach(pk-> valMap.put(tb.get() + "_id", ReflectionUtil.getFieldValue(model, pk)));
            if(pkList.size()<1) {
                return;
            }

            BaseModel bean = applicationContext.getBean(StringUtil.getHumpName(a.table(), true) + "Model", BaseModel.class);
            if(bean == null) {
                return;
            }
            MappedStatement mappedStatement = bean.getMappedStatement(SqlMethod.DELETE_BY_MAP);
            Map<String, Object> map = CollectionUtils.newHashMapWithExpectedSize(1);
            map.put(Constants.COLUMN_MAP, valMap);
            if(StringUtil.isNotEmpty(logicDeleteFiled) && hasField(dbname, table, logicDeleteFiled)) {
                ReflectionUtil.setFieldValue(bean, logicDeleteFiled, logicDeleteValue);
                mappedStatement = bean.getMappedStatement(SqlMethod.LOGIC_DELETE_BY_MAP);
                result.set(SqlHelper.retBool(session.update(mappedStatement.getId(), map)));
            }
            else {
                result.set(SqlHelper.retBool(session.delete(mappedStatement.getId(), map)));
            }

            for (Object obj: datas) {
                BaseModel data = null;
                if(obj instanceof BaseModel<?>) {
                    data = (BaseModel) obj;
                }
                else if(obj instanceof IEntity<?>) {
                    try {
                        data = (BaseModel) JacksonUtil.convert(obj, bean.getClass());
                    } catch (IOException e) {
                        log.error(e.getMessage(), e);
                    }
                }
                if(data == null) {
                    continue;
                }
                ReflectionUtil.setFieldValue(data, StringUtil.getHumpName(a.foreignKey(), true), model.getId());
                MappedStatement insertMappedStatement = data.getMappedStatement(SqlMethod.INSERT_ONE);
                session.insert(insertMappedStatement.getId(), data);
            }
        });
        return result.get();
    }

    public <T extends IEntity<?>, S extends BaseModel<?>> T toVoBean(S model, ActionType actionType) throws IOException {
        if(applicationContext == null) {
            return model.toVo();
        }
        String modelName = model.getClass().getSimpleName();
        if(modelName.endsWith("Model")) {
            modelName = modelName.substring(0, modelName.length()-"Model".length());
        }
        Object result = applicationContext.getBean(StringUtil.getHumpName(modelName, true) + "Vo");
        if(result == null) {
            return model.toVo();
        }

        TargetAction targetAction = result.getClass().getAnnotation(TargetAction.class);
        if(actionType == null || targetAction == null || List.of(targetAction.value()).stream()
                .filter(a-> a.equals(ActionType.ALL) || a.equals(actionType)).findFirst().isPresent()) {
            return (T) JacksonUtil.convert(model.toVo(), result.getClass());
        }
        return model.toVo();
    }
}
